Creating a movie involves several steps. You must first create and open the movie file that is to contain the movie. You then create the tracks and media structures for the movie. You then add samples to the media structures. Finally, you add the movie resource to the movie file. The sample program in this section, CreateWayCoolMovie , demonstrates this process.
This program has been divided into several segments. The main segment, CreateMyCoolMovie , creates and opens the movie file, then invokes other functions to create the movie itself. Once the data has been added to the movie, this function saves the movie in its movie file and closes the file.
The CreateMyCoolMovie function uses the CreateMyVideoTrack and CreateMySoundTrack functions to create the movie's tracks. The CreateMyVideoTrack function creates the video track and the media that contains the track's data. It then collects sample data in the media by calling the AddVideoSamplesToMedia function. Note that this function uses the Image Compression Manager. The CreateMySoundTrack function creates the sound track and the media that contains the sound. It then collects sample data by calling the AddSoundSamplesToMedia function.
Throughout this volume, sound track refers to a QuickTime movie track that contains sound--as opposed to a soundtrack , which denotes the entire audio presentation of a movie as filmgoers know it. Consequently, a soundtrack may be made up of one or more QuickTime sound tracks.
The CreateWayCoolMovie program consists of a number of segments, many of which are not included in this sample. Omitted segments deal with general initialization logic and other common aspects of Macintosh programming. The HandleEditMenu function, shown in Listing 5 , has been included here to show how to initialize the Movie Toolbox with the EnterMovies function.
Listing 5 Creating a movie: The main program
#include <Types.h>
#include <Traps.h>
#include <Menus.h>
#include <Packages.h>
#include <Memory.h>
#include <Errors.h>
#include <Fonts.h>
#include <QuickDraw.h>
#include <Resources.h>
#include <GestaltEqu.h>
#include <FixMath.h>
#include <Sound.h>
#include <string.h>
#include "Movies.h"
#include "ImageCompression.h"
void CheckError(OSErr error, Str255 displayString)
{
if (error == noErr) return;
if (displayString[0] > 0)
DebugStr(displayString);
ExitToShell();
}
void InitMovieToolbox (void)
{
OSErr err;
InitGraf (&qd.thePort);
InitFonts ();
InitWindows ();
InitMenus ();
TEInit ();
InitDialogs (nil);
err = EnterMovies ();
CheckError (err, "\pEnterMovies" );
}
void main( void )
{
InitMovieToolbox ();
CreateMyCoolMovie ();
}
The CreateMyCoolMovie function, shown in Listing 6 , contains the main logic for this program. This function creates and opens a movie file for the new movie. It then establishes a data reference for the movie's data (note that, if your movie's data is stored in the same file as the movie itself, you do not have to create a data reference--set the data reference to 0). This function then calls two other functions, CreateMyVideoTrack and CreateMySoundTrack , to create the tracks for the new movie. Once the tracks have been created, CreateMyCoolMovie adds the new resource to the movie file and closes the movie file.
Listing 6 Creating and opening a movie file
#define kMyCreatorType 'TVOD'
/*
Sample Player's creator type since it is the movie player
of choice. You can use your own creator type, of course.
*/
#define kPrompt "\pEnter movie file name:"
void CreateMyCoolMovie (void)
{
Point where = {100,100};
SFReply theSFReply;
Movie theMovie = nil;
FSSpec mySpec;
short resRefNum = 0;
short resId = 0;
OSErr err = noErr;
SFPutFile (where, "\pEnter movie file name:",
"\pMovie File", nil, &theSFReply);
if (!theSFReply.good) return;
FSMakeFSSpec(theSFReply.vRefNum, 0,
theSFReply.fName, &mySpec);
err = CreateMovieFile (&mySpec,
'TVOD',
smCurrentScript,
createMovieFileDeleteCurFile,
&resRefNum,
&theMovie );
CheckError(err, "\pCreateMovieFile");
CreateMyVideoTrack (theMovie);
CreateMySoundTrack (theMovie);
err = AddMovieResource (theMovie, resRefNum, &resId,
theSFReply.fName);
CheckError(err, "\pAddMovieResource");
if (resRefNum) CloseMovieFile (resRefNum);
DisposeMovie (theMovie);
}
The CreateMyVideoTrack function, shown in Listing 7 , creates a video track in the new movie. This function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. The bulk of this work is done by the AddVideoSamplesToMedia subroutine. Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on InsertMediaIntoTrack ).
Listing 7 Creating a video track
#define kVideoTimeScale 600
#define kTrackStart 0
#define kMediaStart 0
#define kFix1 0x00010000
void CreateMyVideoTrack (Movie theMovie)
{
Track theTrack;
Media theMedia;
OSErr err = noErr;
Rect trackFrame = {0,0,100,320};
theTrack = NewMovieTrack (theMovie,
FixRatio(trackFrame.right,1),
FixRatio(trackFrame.bottom,1),
kNoVolume);
CheckError( GetMoviesError(), "\pNewMovieTrack" );
theMedia = NewTrackMedia (theTrack, VideoMediaType,
600, // Video Time Scale
nil, 0);
CheckError( GetMoviesError(), "\pNewTrackMedia" );
err = BeginMediaEdits (theMedia);
CheckError( err, "\pBeginMediaEdits" );
AddVideoSamplesToMedia (theMedia, &trackFrame);
err = EndMediaEdits (theMedia);
CheckError( err, "\pEndMediaEdits" );
err = InsertMediaIntoTrack (theTrack, 0,/* track start time */
0, /* media start time */
GetMediaDuration (theMedia),
kFix1);
CheckError( err, "\pInsertMediaIntoTrack" );
}
The AddVideoSamplesToMedia function, shown in Listing 8 , creates video data frames, compresses each frame, and adds the frames to the media. This function creates its own video data by calling the DrawAFrame function. Note that this function does not temporally compress the image sequence; rather, the function only spatially compresses each frame individually.
Listing 8 Adding video samples to a media
#define kSampleDuration 240
/* video frames last 240 * 1/600th of a second */
#define kNumVideoFrames 29
#define kNoOffset 0
#define kMgrChoose 0
#define kSyncSample 0
#define kAddOneVideoSample 1
#define kPixelDepth 16
void AddVideoSamplesToMedia (Media theMedia,
const Rect *trackFrame)
{
long maxCompressedSize;
GWorldPtr theGWorld = nil;
long curSample;
Handle compressedData = nil;
Ptr compressedDataPtr;
ImageDescriptionHandle imageDesc = nil;
CGrafPtr oldPort;
GDHandle oldGDeviceH;
OSErr err = noErr;
err = NewGWorld (&theGWorld,
16, /* pixel depth */
trackFrame,
nil,
nil,
(GWorldFlags) 0 );
CheckError (err, "\pNewGWorld");
LockPixels (theGWorld->portPixMap);
err = GetMaxCompressionSize (theGWorld->portPixMap,
trackFrame,
0, /* let ICM choose depth */
codecNormalQuality,
'rle ',
(CompressorComponent) anyCodec,
&maxCompressedSize);
CheckError (err, "\pGetMaxCompressionSize" );
compressedData = NewHandle(maxCompressedSize);
CheckError( MemError(), "\pNewHandle" );
MoveHHi( compressedData );
HLock( compressedData );
compressedDataPtr = StripAddress( *compressedData );
imageDesc = (ImageDescriptionHandle)NewHandle(4);
CheckError( MemError(), "\pNewHandle" );
GetGWorld (&oldPort, &oldGDeviceH);
SetGWorld (theGWorld, nil);
for (curSample = 1; curSample < 30; curSample++)
{
EraseRect (trackFrame);
DrawFrame(trackFrame, curSample);
err = CompressImage (theGWorld->portPixMap,
trackFrame,
codecNormalQuality,
'rle ',
imageDesc,
compressedDataPtr );
CheckError( err, "\pCompressImage" );
err = AddMediaSample(theMedia,
compressedData,
0, /* no offset in data */
(**imageDesc).dataSize,
60, /* frame duration = 1/10 sec */
(SampleDescriptionHandle)imageDesc,
1, /* one sample */
0, /* self-contained samples */
nil);
CheckError( err, "\pAddMediaSample" );
}
SetGWorld (oldPort, oldGDeviceH);
if (imageDesc) DisposeHandle ((Handle)imageDesc);
if (compressedData) DisposeHandle (compressedData);
if (theGWorld) DisposeGWorld (theGWorld);
}
The DrawAFrame function, shown in Listing 9 , creates video data for this movie. This function draws a different frame each time it is invoked, based on the sample number, which is passed as a parameter.
void DrawFrame (const Rect *trackFrame, long curSample)
{
Str255 numStr;
ForeColor( redColor );
PaintRect( trackFrame );
ForeColor( blueColor );
NumToString (curSample, numStr);
MoveTo ( trackFrame->right / 2, trackFrame->bottom / 2);
TextSize ( trackFrame->bottom / 3);
DrawString (numStr);
}
The CreateMySoundTrack function, shown in Listing 10 , creates the movie's sound track. This sound track is not synchronized to the video frames of the movie--rather, it is just a separate sound track that accompanies the video data. This function relies upon an 'snd ' resource for its source sound. The CreateMySoundTrack function uses the CreateSoundDescription function to create the sound description structure for these samples.
As with the CreateMyVideoTrack function discussed earlier, this function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. This function adds the sound samples using a single invocation of the AddMediaSample function. This is possible because all the sound samples are the same size and rely on the same sample description (the SoundDescription structure). If you use this approach, it is often advisable to break up the sound data in the movie, so that the movie plays smoothly. After you create the movie, you can call the FlattenMovie function (described on FlattenMovie ) to create an interleaved version of the movie. Another approach is to call AddMediaSample multiple times, breaking the sound into multiple chunks at that point.
Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on InsertMediaIntoTrack ).
Listing 10 Creating a sound track
#define kSoundSampleDuration 1
#define kSyncSample 0
#define kTrackStart 0
#define kMediaStart 0
#define kFix1 0x00010000
void CreateMySoundTrack (Movie theMovie)
{
Track theTrack;
Media theMedia;
Handle sndHandle = nil;
SoundDescriptionHandle sndDesc = nil;
long sndDataOffset;
long sndDataSize;
long numSamples;
OSErr err = noErr;
sndHandle = GetResource ('snd ', 128);
CheckError (ResError(), "\pGetResource" );
if (sndHandle == nil) return;
sndDesc = (SoundDescriptionHandle) NewHandle(4);
CheckError (MemError(), "\pNewHandle" );
CreateSoundDescription (sndHandle,
sndDesc,
&sndDataOffset,
&numSamples,
&sndDataSize );
theTrack = NewMovieTrack (theMovie, 0, 0, kFullVolume);
CheckError (GetMoviesError(), "\pNewMovieTrack" );
theMedia = NewTrackMedia (theTrack, SoundMediaType,
FixRound ((**sndDesc).sampleRate),
nil, 0);
CheckError (GetMoviesError(), "\pNewTrackMedia" );
err = BeginMediaEdits (theMedia);
CheckError( err, "\pBeginMediaEdits" );
err = AddMediaSample(theMedia,
sndHandle,
sndDataOffset, /* offset in data */
sndDataSize,
1, /* duration of each sound sample */
(SampleDescriptionHandle) sndDesc,
numSamples,
0, /* self-contained samples */
nil );
CheckError( err, "\pAddMediaSample" );
err = EndMediaEdits (theMedia);
CheckError( err, "\pEndMediaEdits" );
err = InsertMediaIntoTrack (theTrack,
0, /* track start time */
0, /* media start time */
GetMediaDuration (theMedia),
kFix1);
CheckError( err, "\pInsertMediaIntoTrack" );
if (sndDesc != nil) DisposeHandle( (Handle)sndDesc);
}
The CreateSoundDescription function, shown in Listing 11 , creates a sound description structure that correctly describes the sound samples obtained from the 'snd ' resource. This function can handle all the sound data formats that are possible in the sound resource. This function uses the GetSndHdrOffset function to locate the sound data in the sound resource.
Listing 11 Creating a sound description
/* Constant definitions */
/*
for the following constants, please consult the Macintosh
Audio Compression and Expansion Toolkit
*/
#define kMACEBeginningNumberOfBytes 6
#define kMACE31MonoPacketSize 2
#define kMACE31StereoPacketSize 4
#define kMACE61MonoPacketSize 1
#define kMACE61StereoPacketSize 2
void CreateSoundDescription (Handle sndHandle,
SoundDescriptionHandlesndDesc,
long *sndDataOffset,
long *numSamples,
long *sndDataSize )
{
long sndHdrOffset = 0;
long sampleDataOffset;
SoundHeaderPtr sndHdrPtr = nil;
long numFrames;
long samplesPerFrame;
long bytesPerFrame;
SignedByte sndHState;
SoundDescriptionPtr sndDescPtr;
*sndDataOffset = 0;
*numSamples = 0;
*sndDataSize = 0;
SetHandleSize( (Handle)sndDesc, sizeof(SoundDescription) );
CheckError(MemError(),"\pSetHandleSize");
sndHdrOffset = GetSndHdrOffset (sndHandle);
if (sndHdrOffset == 0) CheckError(-1, "\pGetSndHdrOffset ");
/* we can use pointers since we don't move memory */
sndHdrPtr = (SoundHeaderPtr) (*sndHandle + sndHdrOffset);
sndDescPtr = *sndDesc;
sndDescPtr->descSize = sizeof (SoundDescription);
/* total size of sound description structure */
sndDescPtr->resvd1 = 0;
sndDescPtr->resvd2 = 0;
sndDescPtr->dataRefIndex = 1;
sndDescPtr->compressionID = 0;
sndDescPtr->packetSize = 0;
sndDescPtr->version = 0;
sndDescPtr->revlevel = 0;
sndDescPtr->vendor = 0;
switch (sndHdrPtr->encode)
{
case stdSH:
sndDescPtr->dataFormat = 'raw ';
/* uncompressed offset-binary data */
sndDescPtr->numChannels = 1;
/* number of channels of sound */
sndDescPtr->sampleSize = 8;
/* number of bits per sample */
sndDescPtr->sampleRate = sndHdrPtr->sampleRate;
/* sample rate */
*numSamples = sndHdrPtr->length;
*sndDataSize = *numSamples;
bytesPerFrame = 1;
samplesPerFrame = 1;
sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea
- (Ptr)sndHdrPtr;
break;
case extSH:
{
ExtSoundHeaderPtr extSndHdrP;
extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr;
sndDescPtr->dataFormat = 'raw ';
/* uncompressed offset-binary data */
sndDescPtr->numChannels = extSndHdrP->numChannels;
/* number of channels of sound */
sndDescPtr->sampleSize = extSndHdrP->sampleSize;
/* number of bits per sample */
sndDescPtr->sampleRate = extSndHdrP->sampleRate;
/* sample rate */
numFrames = extSndHdrP->numFrames;
*numSamples = numFrames;
bytesPerFrame = extSndHdrP->numChannels *
( extSndHdrP->sampleSize / 8);
samplesPerFrame = 1;
*sndDataSize = numFrames * bytesPerFrame;
sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea)
- (Ptr)extSndHdrP;
}
break;
case cmpSH:
{
CmpSoundHeaderPtr cmpSndHdrP;
cmpSndHdrP = (CmpSoundHeaderPtr)sndHdrPtr;
sndDescPtr->numChannels = cmpSndHdrP->numChannels;
/* number of channels of sound */
sndDescPtr->sampleSize = cmpSndHdrP->sampleSize;
/* number of bits per sample before compression */
sndDescPtr->sampleRate = cmpSndHdrP->sampleRate;
/* sample rate */
numFrames = cmpSndHdrP->numFrames;
sampleDataOffset =(Ptr)(&cmpSndHdrP->sampleArea)
- (Ptr)cmpSndHdrP;
switch (cmpSndHdrP->compressionID)
{
case threeToOne:
sndDescPtr->dataFormat = 'MAC3';
/* compressed 3:1 data */
samplesPerFrame = kMACEBeginningNumberOfBytes;
*numSamples = numFrames * samplesPerFrame;
switch (cmpSndHdrP->numChannels)
{
case 1:
bytesPerFrame = cmpSndHdrP->numChannels
* kMACE31MonoPacketSize;
break;
case 2:
bytesPerFrame = cmpSndHdrP->numChannels
* kMACE31StereoPacketSize;
break;
default:
CheckError(-1, "\pCorrupt sound data" );
break;
}
*sndDataSize = numFrames * bytesPerFrame;
break;
case sixToOne:
sndDescPtr->dataFormat = 'MAC6';
/* compressed 6:1 data */
samplesPerFrame = kMACEBeginningNumberOfBytes;
*numSamples = numFrames * samplesPerFrame;
switch (cmpSndHdrP->numChannels)
{
case 1:
bytesPerFrame = cmpSndHdrP->numChannels
* kMACE61MonoPacketSize;
break;
case 2:
bytesPerFrame = cmpSndHdrP->numChannels
* kMACE61StereoPacketSize;
break;
default:
CheckError(-1, "\pCorrupt sound data" );
break;
}
*sndDataSize = (*numSamples) * bytesPerFrame;
break;
default:
CheckError(-1, "\pCorrupt sound data" );
break;
}
} /* switch cmpSndHdrP->compressionID:*/
break; /* of cmpSH: */
default:
CheckError(-1, "\pCorrupt sound data" );
break;
} /* switch sndHdrPtr->encode */
*sndDataOffset = sndHdrOffset + sampleDataOffset;
}
The GetSndHdrOffset function, shown in Listing 12 , parses the specified sound resource and locates the sound data stored in the resource. The GetSndHdrOffset function cruises through a specified 'snd ' resource. It locates the sound data, if any, and returns its type, offset, and size into the resource.
The GetSndHdrOffset function returns an offset instead of a pointer so that the data is not locked in memory. By returning an offset, the calling function can decide when and if it wants the resource locked down to access the sound data.
The first step in finding this data is to determine if the 'snd ' resource is format (type) 1 or format (type) 2. A type 2 is easy, but a type 1 requires that you find the number of 'snth' resource types specified and then skip over each one, including the init option. Once you do this, you have a pointer to the number of commands in the 'snd ' resource. When the function finds the first one, it examines the command to find out if it is a sound data command. Since it is a sound resource, the command also has its dataPointerFlag parameter set to 1. When the function finds a sound data command, it returns its offset and type, and exits.
Do not send the GetSndHdrOffset function a nil handle; if you do, your system will crash.
Listing 12 Parsing a sound resource
typedef SndCommand *SndCmdPtr;
typedef struct
{
short format;
short numSynths;
} Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;
typedef struct
{
short format;
short refCount;
} Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl;
typedef struct
{
short synthID;
long initOption;
} SynthInfo, *SynthInfoPtr;
long GetSndHdrOffset (Handle sndHandle)
{
short howManyCmds;
long sndOffset = 0;
Ptr sndPtr;
if (sndHandle == nil) return 0;
sndPtr = *sndHandle;
if (sndPtr == nil) return 0;
if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat)
{
short synths = ((Snd1HdrPtr)sndPtr)->numSynths;
sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths);
} else
{
sndPtr += sizeof(Snd2Header);
}
howManyCmds = *(short *)sndPtr;
sndPtr += sizeof(howManyCmds);
/*
sndPtr is now at the first sound command--cruise all
commands and find the first soundCmd or bufferCmd
*/
while (howManyCmds > 0)
{
switch (((SndCmdPtr)sndPtr)->cmd)
{
case (soundCmd + dataOffsetFlag):
case (bufferCmd + dataOffsetFlag):
sndOffset = ((SndCmdPtr)sndPtr)->param2;
howManyCmds = 0;/* done, get out of loop */
break;
default: /* catch any other type of commands */
sndPtr += sizeof(SndCommand);
howManyCmds--;
break;
}
} /* done with all commands */
return sndOffset;
} /* of GetSndHdrOffset */